﻿
/*============== simpleIRC - http://matthias.van-eeghem.com/ ==============*
 *                                                                         *
 *  File: EventListener.cs                                                        *
 *  Project: simpleIRC                                                     *
 *  Author(s): Matthias Van Eeghem <matthias@van-eeghem.com>               *
 *                                                                         *
 * ========================================================================*
 *                                                                         *
 *  This program is free software: you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation, either version 3 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 *                                                                         *
===========================================================================*/

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;

namespace simpleIRC.UI
{
    /// <summary>
    ///  This class contains all event handling.
    /// </summary>

    class EventListener
    {
        // This property will hold the reference to the class objects.
        private UserInterface UserInterface = null;
        private MainWindow MainWindow = null;

        /// <summary>
        /// The constructor of the ConnectWindow class
        /// </summary>
        /// <param name="parentWindow">The reference to the mainWindow object</param>

        public EventListener(MainWindow parentWindow)
        {
            MainWindow = parentWindow;
            UserInterface = parentWindow.UserInterface;

            // Add all event listeners here.
            Server.OnMessageEvent += new OnMessage(OnMessage);
            Server.OnJoinEvent += new OnJoin(OnJoin);
            Server.OnPartEvent += new OnPart(OnPart);
            Server.OnQuitEvent += new OnQuit(OnQuit);
            Server.OnClientTimeoutEvent += new OnClientTimeout(OnClientTimeout);
            Server.OnEmptyTopicEvent += new OnEmptyTopic(OnEmptyTopic);
            Server.OnFoundTopicEvent += new OnFoundTopic(OnFoundTopic);
            Server.OnNamesReceivedEvent += new OnNamesReceived(OnNamesReceived);
            Server.OnTopicChangeEvent += new OnTopicChange(OnTopicChange);
            Server.OnNickChangeEvent += new OnNickChange(OnNickChange);
            Server.OnPrivateNoticeEvent += new OnPrivateNotice(OnPrivateNotice);
            Server.OnPrivateMessageEvent += new OnPrivateMessage(OnPrivateMessage);
            Server.OnPrivateActionEvent += new OnPrivateActionSent(OnPrivateActionSent);
            Server.OnChannelNoticeEvent += new OnChannelNotice(OnChannelNotice);
            Server.OnModeChangeEvent += new OnModeChange(OnModeChange);
            Server.OnActionEvent += new OnActionSent(OnAction);
            Server.OnKickEvent += new OnKick(OnKick);
            Server.OnTopicSetByEvent += new OnTopicSetBy(OnTopicSetBy);
            Server.OnInvalidPasswordEvent += new OnInvalidPassword(OnInvalidPassword);
            Server.OnChannelForwardEvent += new OnChannelForward(OnChannelForward);
            Server.OnCannotJoinEvent += new OnCannotJoin(OnCannotJoin);
            Server.OnMotdReceiveEvent += new OnMotdReceive(OnMotdReceive);
            Server.OnMotdStartEvent += new OnMotdStart(OnMotdStart);
            Server.OnMotdEndEvent += new OnMotdEnd(OnMotdEnd);
            Server.OnServerInformationReceivedEvent += new OnServerInformationReceived(OnServerInformationReceived);
            Server.OnCommandsSupportedReceivedEvent += new OnCommandsSupportedReceived(OnCommandsSupportedReceived);
            Server.OnConnectionInitialisedEvent += new OnConnectionInitialised(OnConnectionInitialised);
            Server.OnConnectionFailEvent += new OnConnectionFail(OnConnectionFail);
            Server.OnDisplayedHostReceivedEvent += new OnDisplayedHostReceived(OnDisplayedHostReceived);
            Server.OnPingReplyReceivedEvent += new OnPingReplyReceived(OnPingReplyReceived);
            Server.OnTimeReplyReceivedEvent += new OnTimeReplyReceived(OnTimeReplyReceived);
            Server.OnVersionReplyReceivedEvent += new OnVersionReplyReceived(OnVersionReplyReceived);
            Server.OnNoSuchNickOrChannelEvent += new OnNoSuchNickOrChannel(OnNoSuchNickOrChannel);
            Server.OnNoSuchChannelEvent += new OnNoSuchChannel(OnNoSuchChannel);
            Server.OnNameAlreadyInUseEvent += new OnNameAlreadyInUse(OnNameAlreadyInUse);
            Server.OnRegisteredNickReceivedEvent += new OnRegisteredNickReceived(OnRegisteredNickReceived);
            Server.OnIsAvailableForHelpReceivedEvent += new OnIsAvailableForHelpReceived(OnIsAvailableForHelpReceived);
            Server.OnWhoisInfoReceivedEvent += new OnWhoisInfoReceived(OnWhoisInfoReceived);
            Server.OnUserConnectedFromReceivedEvent += new OnUserConnectedFromReceived(OnUserConnectedFromReceived);
            Server.OnUserIsOperatorReceivedEvent += new OnUserIsOperatorReceived(OnUserIsOperatorReceived);
            Server.OnUserIsIdlingReceivedEvent += new OnUserIsIdlingReceived(OnUserIsIdlingReceived);
            Server.OnEndOfWhoisReceivedEvent += new OnEndOfWhoisReceived(OnEndOfWhoisReceived);
            Server.OnUsersOnChannelsReceivedEvent += new OnUsersOnChannelsReceived(OnUsersOnChannelsReceived);
            Server.OnUserIsLoggedInAsReceivedEvent += new OnUserIsLoggedInAsReceived(OnUserIsLoggedInAsReceived);
            Server.OnUserConnectingFromReceivedEvent += new OnUserConnectingFromReceived(OnUserConnectingFromReceived);
            Server.OnUserIsUsingModesReceivedEvent += new OnUserIsUsingModesReceived(OnUserIsUsingModesReceived);
            Server.OnUnknownCommandReceivedEvent += new OnUnknownCommandReceived(OnUnknownCommandReceived);
            Server.OnInviteCommandReceivedEvent +=new OnInviteCommandReceived(OnInviteCommandReceived);
            Server.OnChannelUrlReceivedEvent += new OnChannelUrlReceived(OnChannelUrlReceived);
        }

        /// <summary>
        /// This method is called when an URL for a channel is received
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="channelName">The channel the URL was received for</param>
        /// <param name="url">The actual URL</param>

        private void OnChannelUrlReceived(int serverIndex, string channelName, string url)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* Channel URL: " + url + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if (Plugin != null) Plugin.OnChannelUrlReceived(serverIndex, channelName, url);
        }

        /// <summary>
        /// This event is called when the user joins a channel that doesn't exist
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="channelName">The (invalid) channel that was joined</param>
        /// <param name="message">The message that the server sent with the information</param>

        private void OnNoSuchChannel(int serverIndex, string channelName, string message)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);
            MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { ChannelWindow.List[Channel.Index].Remove(); }));

            string Output = "* " + channelName + ": " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if (Plugin != null) Plugin.OnNoSuchChannel(serverIndex, channelName, message);
        }

        /// <summary>
        /// Called when the client receives an invite to a channel
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The name of client that sent the invite</param>
        /// <param name="invitedTo">The channel the client was invited to</param>

        private void OnInviteCommandReceived(int serverIndex, string nickName, string invitedTo)
        {
            string Output = MainWindow.Colors.ApplyColor(Brushes.Brown) + "* You have been invited to " + invitedTo + " by " + nickName + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if (Plugin != null) Plugin.OnInviteCommandReceived(serverIndex, nickName, invitedTo);
        }

        /// <summary>
        /// Called when the client receives the unknown command command
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="command">The command that is unknown</param>
        /// <param name="message">The message that was sent</param>

        private void OnUnknownCommandReceived(int serverIndex, string command, string message)
        {
            string Output = "* " + command + ": " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUnknownCommandReceived(serverIndex, command, message);
        }

        /// <summary>
        /// Called when the client receives the modes the target client is using
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="modes">The modes list</param>

        private void OnUserIsUsingModesReceived(int serverIndex, string nickName, string modes)
        {
            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + nickName + " " + modes + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUserIsUsingModesReceived(serverIndex, nickName, modes);
        }

        /// <summary>
        /// Called when the client receives the location the target client is connecting to
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="connectingFrom">The location</param>

        private void OnUserConnectingFromReceived(int serverIndex, string nickName, string connectingFrom)
        {
            string Output = "* " + nickName + " is connecting from " + connectingFrom + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUserConnectingFromReceived(serverIndex, nickName, connectingFrom);
        }

        /// <summary>
        /// Called when the client receives the "logged in as" data from the target client
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="name">The name the target client is logged in as</param>
        /// <param name="message">The message</param>

        private void OnUserIsLoggedInAsReceived(int serverIndex, string nickName, string name, string message)
        {
            string Output = "* " + nickName + " " + message + " " + name + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUserIsLoggedInAsReceived(serverIndex, nickName, name, message);
        }

        /// <summary>
        /// Called when the client receives the channels the target client is on
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="channelList">The channel list</param>

        private void OnUsersOnChannelsReceived(int serverIndex, string nickName, string channelList)
        {
            string Output = "* " + nickName + " on " + channelList + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUsersOnChannelsReceived(serverIndex, nickName, channelList);
        }

        /// <summary>
        /// Called when the client receives end of whois command
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="message">The message</param>

        private void OnEndOfWhoisReceived(int serverIndex, string nickName, string message)
        {
            string Output = "* " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);
            Core.ServerList[serverIndex].Logger.Log("***\r");
            UserInterface.SendTextToCurrentWindow("***\r");

            Core.ServerList[serverIndex].UserExists = false;

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnEndOfWhoisReceived(serverIndex, nickName, message);
        }

        /// <summary>
        /// Called when the client receives the idle command
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="idleTime">The amount of seconds the target client has been idling</param>
        /// <param name="signOnTime">The time the user signed on to the server</param>

        private void OnUserIsIdlingReceived(int serverIndex, string nickName, int idleTime, int signOnTime)
        {
            DateTime Time = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(signOnTime);

            string Output = "* " + nickName + " has been idling for " + idleTime + " seconds and signed on at " + Time.ToShortDateString() + " " + Time.ToShortTimeString() + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUserIsIdlingReceived(serverIndex, nickName, idleTime, signOnTime);
        }

        /// <summary>
        /// Called when the client receives the user is operator command
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="message">The message</param>

        private void OnUserIsOperatorReceived(int serverIndex, string nickName, string message)
        {
            string Output = "* " + nickName + " " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUserIsOperatorReceived(serverIndex, nickName, message);
        }

        /// <summary>
        /// Called when the client receives the user is connecting to command
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="server">The server the user is connected to</param>
        /// <param name="info">The info about the server</param>

        private void OnUserConnectedFromReceived(int serverIndex, string nickName, string server, string info)
        {
            string Output = "* " + nickName + " is connected to " + server + " (" + info + ")\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnUserConnectedFromReceived(serverIndex, nickName, server, info);
        }

        /// <summary>
        /// Called when the client receives the userName/hostName of the target client
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="name">The name of the client</param>
        /// <param name="hostName">The hostName of the target client</param>
        /// <param name="userName">The userName of the target client</param>

        private void OnWhoisInfoReceived(int serverIndex, string nickName, string name, string hostName, string userName)
        {
            string Output = "* " + nickName + " is " + name + "@" + hostName + " * " + userName + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow("***\r");
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnWhoisInfoReceived(serverIndex, nickName, name, hostName, userName);
        }

        /// <summary>
        /// Called when the client receives whether or not the target client is an operator
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="message">The message</param>

        private void OnIsAvailableForHelpReceived(int serverIndex, string nickName, string message)
        {
            string Output = "* " + nickName + " " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnIsAvailableForHelpReceived(serverIndex, nickName, message);
        }

        /// <summary>
        /// Called when the client receives whether or not the target client is registered
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the target client</param>
        /// <param name="message">The message</param>

        private void OnRegisteredNickReceived(int serverIndex, string nickName, string message)
        {
            string Output = "* " + nickName + " " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnRegisteredNickReceived(serverIndex, nickName, message);
        }

        /// <summary>
        /// Called when the server sends the nickName already in use message
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname of the client</param>
        /// <param name="message">The message the server sent</param>

        private void OnNameAlreadyInUse(int serverIndex, string nickName, string message)
        {
            string Output = "* " + nickName + ": " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            // Wait 2 seconds
            Thread.Sleep(2000);

            // Set the current name to the optional name, and reconnect
            Server Server = Core.ServerList[serverIndex];
            Server.Nick = Server.OptionalName;
            Server.Connect();

            foreach (PluginInterface Plugin in MainWindow.PluginList) if (Plugin != null) Plugin.OnNameAlreadyInUse(serverIndex, nickName, message);
        }

        /// <summary>
        /// Called when the server sends the 401 no such nick or channel message.
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="target">The nick or channel that couldn't be found</param>
        /// <param name="message">The message that was sent</param>

        private void OnNoSuchNickOrChannel(int serverIndex, string target, string message)
        {
            string Output = "* " + target + ": " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);
        }

        /// <summary>
        /// Called when the user receives the PING reply from another client
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname that sent the reply</param>
        /// <param name="ping">The time the command was sent (as epoch time)</param>

        private void OnPingReplyReceived(int serverIndex, string nickName, string ping)
        {
            int EpochTime = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
            int Difference = Convert.ToInt32(ping) - EpochTime;

            string Output = "[Ping reply from: " + nickName + "]: " + Difference.ToString() + " seconds\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);
        }

        /// <summary>
        /// Called when the user receives the TIME reply from another client
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname that sent the reply</param>
        /// <param name="time">The time the command was sent (as epoch time)</param>

        private void OnTimeReplyReceived(int serverIndex, string nickName, string time)
        {
            string Output = "[Time reply from: " + nickName + "]: " + time + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);
        }

        /// <summary>
        /// Called when the user receives the VERSION reply from another client
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="nickName">The nickname that sent the reply</param>
        /// <param name="version">The client version as string</param>

        private void OnVersionReplyReceived(int serverIndex, string nickName, string version)
        {
            string Output = "[Version reply from: " + nickName + "]: " + version + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            UserInterface.SendTextToCurrentWindow(Output);
        }

        /// <summary>
        /// Called when the client receives the your hostName is event
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="hostName">The displayed hostName</param>

        private void OnDisplayedHostReceived(int serverIndex, string hostName)
        {
            string Output = "Your displayed hostName is: " + hostName + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnDisplayedHostReceived(serverIndex, hostName);
        }
        
        /// <summary>
        /// Called when the server sends all the commands supported to the client
        /// </summary>
        /// <param name="serverIndex">The index of the server</param>
        /// <param name="commandsSupported">The list of the commands supported</param>

        private void OnCommandsSupportedReceived(int serverIndex, string commandsSupported)
        {
            string Output = "Supported: " + commandsSupported + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnCommandsSupportedReceived(serverIndex, commandsSupported);
        }

        /// <summary>
        /// Called when the client times out from the server
        /// </summary>
        /// <param name="serverIndex">The serverIndex</param>

        private void OnClientTimeout(int serverIndex)
        {
            Server Server = Core.ServerList[serverIndex];

            // Could not resolve hostName 
            string Output = "* Lost connection to " + Server.Hostname + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            Output = "* Trying to reconnect..\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            // Loop through all channels
            for (int i = 0; i < Core.ChannelList.Count; i++)
            {
                Channel Channel = Core.ChannelList[i];

                if(Channel != null && Channel.Server != null)
                {
                    if (Channel.Server.Index == serverIndex)
                    {
                        Output = "* Lost connection to " + Server.Hostname + "\r";
                        Core.ServerList[serverIndex].Logger.Log(Output);
                        ChannelWindow.List[i].SendText(Output);
                    }
                }
            }

            // Try reconnecting
            Server.Connect();

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnClientTimeout(serverIndex);
        }

        /// <summary>
        /// Called when the user couldn't connect to the server
        /// </summary>
        /// <param name="serverIndex">The server index that didn't connect</param>
        /// <param name="errorCode">The error code</param>

        private void OnConnectionFail(int serverIndex, int errorCode)
        {
            Server Server = Core.ServerList[serverIndex];

            // Check which error code we're dealing with
            if(errorCode == 11001)
            {
                // Not connected to the internet
                string Output = "* Failed to connect to " + Core.ServerList[serverIndex].Hostname + " (are you connected to the internet?)\r";
                Core.ServerList[serverIndex].Logger.Log(Output);
                ServerWindow.List[serverIndex].SendText(Output);

                Output = "* Retrying\r";
                Core.ServerList[serverIndex].Logger.Log(Output);
                ServerWindow.List[serverIndex].SendText(Output);

                // Wait 5 seconds
                Thread.Sleep(5000);

                // Try reconnecting
                Server.Connect();
            }

            if (errorCode == 10060)
            {
                // Could not resolve hostName 
                string Output = "* Failed to connect to " + Core.ServerList[serverIndex].Hostname + " (could not resolve hostName)\r";
                Core.ServerList[serverIndex].Logger.Log(Output);
                ServerWindow.List[serverIndex].SendText(Output);

                Output = "* Retrying\r";
                Core.ServerList[serverIndex].Logger.Log(Output);
                ServerWindow.List[serverIndex].SendText(Output);

                // Try reconnecting
                Server.Connect();
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnConnectionFail(serverIndex, errorCode);
        }

        /// <summary>
        /// Called when a connection is initialised
        /// </summary>
        /// <param name="serverIndex">The server index</param>

        private void OnConnectionInitialised(int serverIndex)
        {
            string Output = "* Attempting to connect to " + Core.ServerList[serverIndex].Hostname + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnConnectionInitialised(serverIndex);
        }

        /// <summary>
        /// Called when the server sends information to the client
        /// </summary>
        /// <param name="serverIndex">The server index</param>
        /// <param name="message">The message that the server sent</param>

        private void OnServerInformationReceived(int serverIndex, string message)
        {
            string Output = message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnServerInformationReceived(serverIndex, message);
        }

        /// <summary>
        /// Called when the server sends the start of the MOTD command
        /// </summary>
        /// <param name="serverIndex">The index the server is on</param>
        /// <param name="motdMessage">The message</param>

        private void OnMotdStart(int serverIndex, string motdMessage)
        {
            string Output = motdMessage + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnMotdStart(serverIndex, motdMessage);
        }

        /// <summary>
        /// Called when the server sends the MOTD message to the client
        /// </summary>
        /// <param name="serverIndex">The index of the server</param>
        /// <param name="motdMessage">The message passed on</param>

        private void OnMotdReceive(int serverIndex, string motdMessage)
        {
            string Output = motdMessage + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnMotdReceive(serverIndex, motdMessage);
        }

        /// <summary>
        /// Called when the server sends the end of the MOTD command
        /// </summary>
        /// <param name="serverIndex">The index the server is on</param>
        /// <param name="motdMessage">The message</param>

        private void OnMotdEnd(int serverIndex, string motdMessage)
        {
            Server Server = Core.ServerList[serverIndex];

            string Output = motdMessage + "\r";
            Server.Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            // Loop through all the channels and rejoin them
            // We're rejoin the channels here because we want to be fully connected to the server
            // before rejoining any channels. If this is the first time connecting, nothing will happen
            // if the user timed out, or disconnected and then reconnected, this code will rejoin any channel.

            for (int i = 0; i < Core.ChannelList.Count; i++)
            {
                Channel Channel = Core.ChannelList[i];

                if (Channel != null && Channel.Server.Index == serverIndex)
                {
                    ChannelWindow.List[i].SendText("* Reconnected.\r");
                    Channel.Join();

                    // Only update the user list if the user is on that channel.
                    if (MainWindow.CurrentChannelWindow != -1 && Channel.Name.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
                    {
                        MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { MainWindow.UsersTreeView.Items.Clear(); }));
                        new Task(() => { ChannelWindow.List[Channel.Index].LoadUsers(); }).Start();
                    }
                }
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnMotdEnd(serverIndex, motdMessage);
        }

        /// <summary>
        /// Called when the client couldn't join the channel
        /// </summary>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The index the server</param>
        /// <param name="message">The reason why the client failed to join the channel</param>

        private void OnCannotJoin(string channelName, int serverIndex, string message)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);
            MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { ChannelWindow.List[Channel.Index].Remove(); }));

            string Output = "* Cannot join " + channelName + ": " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnCannotJoin(channelName, serverIndex, message);
        }

        /// <summary>
        /// Called when the client is forwarded to another channel
        /// </summary>
        /// <param name="channelName">The channel he attempted to join</param>
        /// <param name="forward">The channel he should be forwarded to</param>
        /// <param name="serverIndex">The server index</param>
        /// <param name="message">message sent by the server</param>

        private void OnChannelForward(string channelName, string forward, int serverIndex, string message)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            if(Channel.Index != -1)
            {
                MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() 
                {
                    ChannelWindow.List[Channel.Index].Remove();
                    new ChannelWindow(forward, serverIndex, (Core.ChannelList.Count - 1), MainWindow).Create();
                }));

                string Output = "* " + channelName + " is forwarding to " + forward + "\r";
                Core.ServerList[serverIndex].Logger.Log(Output);
                ServerWindow.List[serverIndex].SendText(Output);
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnChannelForward(channelName, forward, serverIndex, message);
        }

        /// <summary>
        /// Called when a message is said on a channel or in a server window.
        /// </summary>
        /// <param name="channelName">The channel something was said (empty channel means it's supposed to go in a server window)</param>
        /// <param name="serverIndex">The server index the channel is on</param>
        /// <param name="sender">The name of the sender (empty means server window)</param>
        /// <param name="message">The message that was sent</param>
        
        private void OnMessage(string channelName, int serverIndex, string sender, string message)
        {
            // Check if it's supposed to go into a server or channel window.
            if (channelName != "")
            {
                Channel Channel = Core.GetChannelObject(channelName, serverIndex);
                string UserLevel = Channel.GetUserHighestRights(sender);
                string Output = "<" + UserLevel + sender + "> " + message + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";

                Channel.Logger.Log(Output);
                ChannelWindow.List[Channel.Index].SendText(Output);

                if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() != Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower())
                {
                    MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { ChannelWindow.List[Channel.Index].ChannelTreeViewItem.Foreground = Brushes.Red; }));
                }
            }
            else
            {
                Core.ServerList[serverIndex].Logger.Log(message + "\r");
                ServerWindow.List[serverIndex].SendText(message + "\r");
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnMessageSent(channelName, serverIndex, sender, message);
        }

        /// <summary>
        /// Called when a private message is sent to the client
        /// </summary>
        /// <param name="sender">The name of the person who sent the message</param>
        /// <param name="serverIndex">The server index the person who sent the message is on</param>
        /// <param name="message">The message that the user sent</param>
        
        private void OnPrivateMessage(string sender, int serverIndex, string message)
        {
            // Make the query list visible, and if the query window isn't visible yet, create it.
            MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() 
            {
                QueryList.ListOfQueries[MainWindow.CurrentServerWindow].QueryListTreeViewItem.Visibility = Visibility.Visible;
                Query Query = Core.GetQueryObject(sender, serverIndex);

                if (Query == null)
                {
                    MessageBox.Show("Query was null, not any more");
				    Query = new Query(serverIndex, sender);
                    new QueryWindow(sender, serverIndex, (Core.QueryList.Count - 1), MainWindow).Create();
                }

                string OutputLine = "<" + sender + "> " + message + "\r";
                Query.Logger.Log(OutputLine);
                QueryWindow.List[Query.Index].SendText(OutputLine);
            }));

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnPrivateMessage(sender, serverIndex, message);
        }

        /// <summary>
        /// Called when a user enters an invalid password for a passworded channel.
        /// </summary>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The serverIndex the channel is on</param>
        /// <param name="message">The message sent by the server</param>

        private void OnInvalidPassword(string channelName, int serverIndex, string message)
        {
            // Send a message telling the user what went wrong when joining the channel
            string Output =  "* " + channelName + " " + message + "\r";
            Core.ServerList[serverIndex].Logger.Log(Output);
            ServerWindow.List[serverIndex].SendText(Output);

            MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { ChannelWindow.List[Core.GetChannelObject(channelName, serverIndex).Index].Remove(); }));
            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnInvalidPassword(channelName, serverIndex, message);
        }

        /// <summary>
        /// Called when the client receives a name and time when the topic was set.
        /// </summary>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The server index the channel is on</param>
        /// <param name="name">The name of the user that set the topic</param>
        /// <param name="timeSet">Time when the topic was set (epoch time)</param>

        private void OnTopicSetBy(string channelName, int serverIndex, string name, int timeSet)
        {
            // Get the time as a string
            DateTime DateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(timeSet);
            // Convert it to the local time.
            DateTime = DateTime.ToLocalTime();

            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            // Format the output
            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* Set by " + name + " on " + DateTime.ToShortDateString() + " at " + DateTime.ToLongTimeString() + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnTopicSetBy(channelName, serverIndex, name, timeSet);
        }

        /// <summary>
        /// Called when an action is sent privately to the client
        /// </summary>
        /// <param name="sender">The name of the person who sent the action</param>
        /// <param name="serverIndex">The server index the person who sent the action is on</param>
        /// <param name="action">The action that the user sent</param>
        
        private void OnPrivateActionSent(string sender, int serverIndex, string action)
        {
            // Make the query list visible, and if the query window isn't visible yet, create it.
            MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
            {
                QueryList.ListOfQueries[MainWindow.CurrentServerWindow].QueryListTreeViewItem.Visibility = Visibility.Visible;
                Query Query = Core.GetQueryObject(sender, serverIndex);

                if (Query == null)
                {
				    Query = new Query(serverIndex, sender);
                    new QueryWindow(sender, serverIndex, (Core.QueryList.Count - 1), MainWindow).Create();
                }

                string OutputLine = "* " + sender + " " + action + "\r";
                Query.Logger.Log(OutputLine);
                QueryWindow.List[Query.Index].SendText(OutputLine);
            }));

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnPrivateActionSent(sender, serverIndex, action);
        }

        /// <summary>
        /// Called when a user sends a notice to the client.
        /// </summary>
        /// <param name="sender">The name of the sender</param>
        /// <param name="serverIndex">The server index that the channel is on</param>
        /// <param name="message">The message that was sent</param>

        private void OnPrivateNotice(string sender, int serverIndex, string message)
        {
            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkBlue) + "* " + sender + " has sent you a notice: " + message + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            UserInterface.SendTextToCurrentWindow(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnPrivateNotice(sender, serverIndex, message);
        }

        /// <summary>
        /// Called when a user is kicked from a channel
        /// </summary>
        /// <param name="channelName">The channel the user was kicked from</param>
        /// <param name="serverIndex">The index the channel is on</param>
        /// <param name="kickerName">The user that executed the kick</param>
        /// <param name="kickeeName">The user that was kicked</param>
        /// <param name="highestLevelBeforeKick">The highest level the user had before being kicked from the channel</param>
        /// <param name="reason">The reason he was kicked for</param>

        private void OnKick(string channelName, int serverIndex, string kickerName, string kickeeName, string highestLevelBeforeKick, string reason)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            // Check if the user who was kicked was the user itself.
            if (kickeeName == Core.ServerList[serverIndex].Nick)
            {
                // Remove the channel window
                MainWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { ChannelWindow.List[Channel.Index].Remove(); }));

                // Now inform the user.
                string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* You were kicked from " + channelName + " by " + kickerName + " (Reason: " + reason + ")" + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
                ServerWindow.List[serverIndex].SendText(Output);

                Channel.Logger.Log(Output);
            }

            // Otherwise, just send the output.
            else
            {
                string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + kickeeName + " was kicked by " + kickerName + " (Reason: " + reason + ")" + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
                Channel.Logger.Log(Output);
                ChannelWindow.List[Channel.Index].SendText(Output);
                
                // Only update the user list if the user is on that channel.
                if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
                {
                    // Copy the items of the UsersTreeView to an array as we cannot modify an enum while looping through it
                    Array TreeViewItemsCopy = Array.CreateInstance(typeof(TreeViewItem), MainWindow.UsersTreeView.Items.Count);
                    MainWindow.UsersTreeView.Items.CopyTo(TreeViewItemsCopy, 0);

                    // We now search the TreeViewItem of the user that was kicked from the channel so we can remove it
                    // this way we do not have to reload the entire nameslist. We need the highest level the user had before
                    // being kicked in order to find the correct item.
                    string NameWithRights = highestLevelBeforeKick + kickeeName;

                    // Loop through each item
                    foreach (TreeViewItem Item in TreeViewItemsCopy)
                    {
                        // We need to invoke the dispatcher as we're accessing the UI from a different thread
                        MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
                        {
                            // We found the correct item, simply remove it
                            if (Item.Header.ToString() == NameWithRights)
                                MainWindow.UsersTreeView.Items.Remove(Item);
                        }));
                    }
                }
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnKickSent(channelName, serverIndex, kickerName, kickeeName, reason, highestLevelBeforeKick);
        }

        /// <summary>
        /// Called when somebody does an action on a channel.
        /// </summary>
        /// <param name="channelName">The channel the action was sent to</param>
        /// <param name="serverIndex">The index the channel is on</param>
        /// <param name="userName">The name of the user</param>
        /// <param name="action">The action that was sent</param>

        private void OnAction(string channelName, int serverIndex, string userName, string action)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);
            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkBlue) + "* " + userName + " " + action + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnActionSent(channelName, serverIndex, userName, action);
        }

        /// <summary>
        /// Called when a mode is changed.
        /// </summary>
        /// <param name="channelName">The channel the mode was changed for (empty if it's a server)</param>
        /// <param name="serverIndex">The index that channel is on</param>
        /// <param name="nickName">The user that changed the mode</param>
        /// <param name="mode">The mode that was changed</param>
        /// <param name="names">An array of users the mode change had effect on</param>
        /// <param name="highestLevelBeforeModeChange">An array of strings containing the highest rights of each user in the names array before the mode change</param>

        private void OnModeChange(string channelName, int serverIndex, string nickName, string mode, string[] names, string[] highestLevelBeforeModeChange)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            if (Channel == null)
            {
                string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + nickName + " sets mode: " + mode + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
                Core.ServerList[serverIndex].Logger.Log(Output);
                ServerWindow.List[serverIndex].SendText(Output);
            }
            else
            {
                string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + nickName + " sets mode: " + mode + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
                Channel.Logger.Log(Output);
                ChannelWindow.List[Channel.Index].SendText(Output);

                // Only update the user list if the user is on that channel.
                if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
                {
                    // Copy the items of the UsersTreeView to an array as we cannot modify an enum while looping through it
                    Array TreeViewItemsCopy = Array.CreateInstance(typeof(TreeViewItem), MainWindow.UsersTreeView.Items.Count);
                    MainWindow.UsersTreeView.Items.CopyTo(TreeViewItemsCopy, 0);

                    // Make sure the names array isn't null
                    if (names == null) return;

                    for (int i = 0; i < names.Length; i++)
                    {
                        // We now search the TreeViewItem of each user that has a different level due to the mode change, so we can edit it
                        // this way we do not have to reload the entire nameslist. We need the highest level the user had before
                        // the mode change in order to find the correct item.
                        string NameWithRights = highestLevelBeforeModeChange[i] + names[i];

                        // Loop through each item
                        foreach (TreeViewItem Item in TreeViewItemsCopy)
                        {
                            // We need to invoke the dispatcher as we're accessing the UI from a different thread
                            MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
                            {
                                // We found the correct item, simply remove it
                                if (Item.Header.ToString() == NameWithRights)
                                {
                                    // Get the user's new level
                                    string NewLevel = Channel.GetUserHighestRights(names[i]);

                                    // Change the Item's header
                                    Item.Header = NewLevel + names[i];

                                    // We prepend the Item.Name property with a letter indicating the user's level
                                    // We do this so we can sort the entire UsersTreeView on the Item.Name property
                                    char SortCharacter = (char)MainWindow.SortCharacters.NoRightsCharacter;

                                    if (NewLevel.Length > 0)
                                    {
                                        if (NewLevel[0] == '~') SortCharacter = (char)MainWindow.SortCharacters.OwnerCharacter;
                                        else if (NewLevel[0] == '&') SortCharacter = (char)MainWindow.SortCharacters.SuperOpCharacter;
                                        else if (NewLevel[0] == '@') SortCharacter = (char)MainWindow.SortCharacters.OpCharacter;
                                        else if (NewLevel[0] == '%') SortCharacter = (char)MainWindow.SortCharacters.HalfopCharacter;
                                        else if (NewLevel[0] == '+') SortCharacter = (char)MainWindow.SortCharacters.VoiceCharacter;
                                    }

                                    // Set the item name, but remove all invalid characters from the users name
                                    Item.Name = System.Text.RegularExpressions.Regex.Replace((SortCharacter + names[i]), "[^a-zA-Z0-9]", "");
                                }

                                // If this is the last name in the loop, we sort the UsersTreeView alphabetically, sorting on the TreeViewItem.Name property
                                // We only sort the last one because it will resort all of them, and we don't need to resort it names.Length amount of times
                                if (i == (names.Length - 1))
                                    MainWindow.UsersTreeView.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Name", System.ComponentModel.ListSortDirection.Ascending));
                            }));
                        }
                    }
                }
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnModeChange(channelName, serverIndex, nickName, mode, names, highestLevelBeforeModeChange);
        }

        /// <summary>
        /// Called when a user sends a notice to a channel.
        /// </summary>
        /// <param name="sender">The name of the sender</param>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The server index that the channel is on</param>
        /// <param name="message">The message that was sent</param>

        private void OnChannelNotice(string sender, string channelName, int serverIndex, string message)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            if(Channel != null)
            {
                string Output = "* " + sender + " has sent a notice to " + channelName + ": " + message + "\r";

                Channel.Logger.Log(Output);
                ChannelWindow.List[Channel.Index].SendText(Output);

                foreach (PluginInterface Plugin in MainWindow.PluginList) if (Plugin != null) Plugin.OnChannelNotice(sender, channelName, serverIndex, message);
            }
        }

        /// <summary>
        /// Called when a user joins a channel.
        /// </summary>
        /// <param name="channelName">The channel he joined</param>
        /// <param name="serverIndex">The server index that channel is on.</param>
        /// <param name="userName">The name of the user</param>
        
        private void OnJoin(string channelName, int serverIndex, string userName)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + userName + " has joined " + channelName + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            // Only update the user list if the user is on that channel.
            if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
            {
                // We need to invoke the dispatcher as we're accessing the UI from a different thread
                MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() 
                {
                    TreeViewItem NewItem = new TreeViewItem();
                    NewItem.Header = userName;
                    // We prepend the Item.Name property with a letter indicating the user's level
                    // We do this so we can sort the entire UsersTreeView on the Item.Name property
                    // "f" indicates that the user has no level
                    NewItem.Name = "f" + System.Text.RegularExpressions.Regex.Replace(userName, "[^a-zA-Z0-9]", "");

                    // Add the item to the treeview
                    MainWindow.UsersTreeView.Items.Add(NewItem);

                    // Now sort the UsersTreeView
                    MainWindow.UsersTreeView.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Name", System.ComponentModel.ListSortDirection.Ascending));
                }));
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnJoinSent(channelName, serverIndex, userName);
        }

        /// <summary>
        /// Called when a user parts a channel.
        /// </summary>
        /// <param name="channelName">The channel he left</param>
        /// <param name="serverIndex">The server index that channel is on</param>
        /// <param name="nickName">The name of the user</param>
        /// <param name="highestLevel">The highest level (if any) the user had before parting the channel</param>
        /// <param name="reason">Any optional reason he quit with</param>
        
        private void OnPart(string channelName, int serverIndex, string nickName, string highestLevel, string reason)
        {
            // If the reason is empty, format it a bit differently (looks a bit nicer)
            string Output = (reason == "") ? MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + nickName + " has left " + channelName + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r" : MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + nickName + " has left " + channelName + " (Reason: " + reason + ")" + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";

            Channel Channel = Core.GetChannelObject(channelName, serverIndex);
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            // Only update the user list if the user is on that channel.
            if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
            {
                // Copy the items of the UsersTreeView to an array as we cannot modify an enum while looping through it
                Array TreeViewItemsCopy = Array.CreateInstance(typeof(TreeViewItem), MainWindow.UsersTreeView.Items.Count);
                MainWindow.UsersTreeView.Items.CopyTo(TreeViewItemsCopy, 0);

                // We now search the TreeViewItem of the user that left the channel so we can remove it from the TreeView
                // this way we do not have to reload the entire nameslist. We need the highest level the user had before
                // leaving in order to find the correct item.
                string NameWithRights = highestLevel + nickName;

                // Loop through each item
                foreach (TreeViewItem Item in TreeViewItemsCopy)
                {
                    // We need to invoke the dispatcher as we're accessing the UI from a different thread
                    MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
                    {
                        // We found the correct item, simply remove it
                        if (Item.Header.ToString() == NameWithRights)
                            MainWindow.UsersTreeView.Items.Remove(Item);
                    }));
                }
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnPartSent(channelName, serverIndex, nickName, highestLevel, reason);
        }

        /// <summary>
        /// Called when a user quits.
        /// </summary>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The server index that channel is on</param>
        /// <param name="nickName">The nickname of the user</param>
        /// <param name="userName">The userName of the user</param>
        /// <param name="hostName">The host of the user</param>
        /// <param name="highestLevel">The highest level (if any) the user had before parting the channel</param>
        /// <param name="reason">Any optional reason he quit with</param>

        private void OnQuit(string channelName, int serverIndex, string nickName, string userName, string hostName, string highestLevel, string reason)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            // Format the output
            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkBlue) + "* " + nickName + " (" + userName + "@" + hostName + ") has quit (Quit: " + reason + ")" + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Core.GetChannelObject(channelName, serverIndex).Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            // Only update the user list if the user is on that channel.
            if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
            {
                // Copy the items of the UsersTreeView to an array as we cannot modify an enum while looping through it
                Array TreeViewItemsCopy = Array.CreateInstance(typeof(TreeViewItem), MainWindow.UsersTreeView.Items.Count);
                MainWindow.UsersTreeView.Items.CopyTo(TreeViewItemsCopy, 0);

                // We now search the TreeViewItem of the user that quit from the server so we can remove it from the TreeView
                // this way we do not have to reload the entire nameslist. We need the highest level the user had before
                // quitting in order to find the correct item.
                string NameWithRights = highestLevel + nickName;

                // Loop through each item
                foreach (TreeViewItem Item in TreeViewItemsCopy)
                {
                    // We need to invoke the dispatcher as we're accessing the UI from a different thread
                    MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
                    {
                        // We found the correct item, simply remove it
                        if (Item.Header.ToString() == NameWithRights)
                            MainWindow.UsersTreeView.Items.Remove(Item);
                    }));
                }
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnQuitSent(channelName, serverIndex, nickName, userName, hostName, highestLevel, reason);
        }

        /// <summary>
        /// After a user joins a channel, the server either sends the topic name or an empty topic, this is called when the latter is sent.
        /// </summary>
        /// <param name="channelName">The channel the topic was sent from</param>
        /// <param name="serverIndex">The server index that channel is on</param>

        private void OnEmptyTopic(string channelName, int serverIndex)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* You have joined " + channelName + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* No topic set" + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnEmptyTopic(channelName, serverIndex);
        }

        /// <summary>
        /// After a user joins a channel, the server either sends the topic name or an empty topic, this is called when the first is sent.
        /// </summary>
        /// <param name="channelName">The channel the topic was sent from</param>
        /// <param name="serverIndex">The server index that channel is on</param>
        /// <param name="topic">The topic as a string</param>

        private void OnFoundTopic(string channelName, int serverIndex, string topic)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* You have joined " + channelName + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* Topic is: " + topic + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnFoundTopic(channelName, serverIndex, topic);
        }

        /// <summary>
        /// Called when the server sends the list of names to the application
        /// </summary>
        /// <param name="channelName">The channel the names were sent from</param>
        /// <param name="serverIndex">The server index that channel is on</param>
        /// <param name="names">An array with all the names</param>

        private void OnNamesReceived(string channelName, int serverIndex, string[] names)
        {
            // Only update the user list if the user is on that channel.
            if (MainWindow.CurrentChannelWindow != -1 && Core.ChannelList[MainWindow.CurrentChannelWindow] != null && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
            {
                Channel Channel = Core.GetChannelObject(channelName, serverIndex);

                MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() { MainWindow.UsersTreeView.Items.Clear(); }));
                new Task(() => { ChannelWindow.List[Channel.Index].LoadUsers(); }).Start();
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnNamesReceived(channelName, serverIndex, names);
        }

        /// <summary>
        /// Called when a topic is changed for a certain channel.
        /// </summary>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The index that channel is on</param>
        /// <param name="userName">The user that changed the topic</param>
        /// <param name="newTopic">The new topic</param>
        
        private void OnTopicChange(string channelName, int serverIndex, string userName, string newTopic)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + userName + " changes topic to '" + newTopic + "'" + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Core.GetChannelObject(channelName, serverIndex).Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnTopicChange(channelName, serverIndex, userName, newTopic);
        }

        /// <summary>
        /// Called when a user changes his or her nickName.
        /// </summary>
        /// <param name="channelName">The name of the channel</param>
        /// <param name="serverIndex">The index that channel is on</param>
        /// <param name="oldName">The user that changed his/her name</param>
        /// <param name="newName">The new name of that user</param>
        
        private void OnNickChange(string channelName, int serverIndex, string oldName, string newName)
        {
            Channel Channel = Core.GetChannelObject(channelName, serverIndex);

            string Output = MainWindow.Colors.ApplyColor(Brushes.DarkGreen) + "* " + oldName + " is now known as " + newName + MainWindow.Colors.ApplyColor(Brushes.Black) + "\r";
            Channel.Logger.Log(Output);
            ChannelWindow.List[Channel.Index].SendText(Output);

            // Only update the user list if the user is on that channel.
            if (MainWindow.CurrentChannelWindow != -1 && channelName.ToLower() == Core.ChannelList[MainWindow.CurrentChannelWindow].Name.ToLower() && MainWindow.CurrentServerWindow == serverIndex)
            {
                // Copy the items of the UsersTreeView to an array as we cannot modify an enum while looping through it
                Array TreeViewItemsCopy = Array.CreateInstance(typeof(TreeViewItem), MainWindow.UsersTreeView.Items.Count);
                MainWindow.UsersTreeView.Items.CopyTo(TreeViewItemsCopy, 0);

                // We now search the TreeViewItem  the user that changed his/her name so we can edit the Header and Name property,
                // this way we do not have to reload the entire nameslist. We need the highest level the user had before
                // changing his or her name in order to find the correct item.
                string NameWithRights = Channel.GetUserHighestRights(newName) + oldName;

                // Loop through each item
                foreach (TreeViewItem Item in TreeViewItemsCopy)
                {
                    MainWindow.UsersTreeView.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
                    {
                        // We found the correct item
                        if (Item.Header.ToString() == NameWithRights)
                        {
                            char SortCharacter = Item.Name[0];
                            string Name = Channel.GetUserHighestRights(newName) + newName;

                            Item.Header = Name;
                            // Filter out the invalid characters
                            Item.Name = SortCharacter + System.Text.RegularExpressions.Regex.Replace(Name, "[^a-zA-Z0-9]", "");

                            // Resort the TreeViewList on the Name property
                            MainWindow.UsersTreeView.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Name", System.ComponentModel.ListSortDirection.Ascending));
                        }
                    }));
                }
            }

            foreach (PluginInterface Plugin in MainWindow.PluginList) if(Plugin != null) Plugin.OnNickChange(channelName, serverIndex, oldName, newName);
        }
    }
}
